using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.CompilerServices;
using Pinwheel.LSerialize.Formatters;

namespace Pinwheel.LSerialize
{
    public static partial class LFormatterProvider
    {
        static readonly ConcurrentDictionary<Type, ILFormatter> formatters = new ConcurrentDictionary<Type, ILFormatter>(Environment.ProcessorCount, 150);
        
        static partial void RegisterExtensionFormatters();
        
        static LFormatterProvider()
        {
            // Initialize builtin on startup
            RegisterBuiltinFormatters();
            // Extension for others
            RegisterExtensionFormatters();
        }
               
        public static bool IsRegistered<T>() => Check<T>.registered;
        public static void Register<T>(LFormatter<T> formatter)
        {
            Check<T>.registered = true;
            formatters[typeof(T)] = formatter;
            Cache<T>.formatter = formatter;
        }
        
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal static LFormatter<T> GetFormatter<T>()
        {
            return Cache<T>.formatter;
        }
        
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal static ILFormatter GetFormatter(Type type)
        {
            if (formatters.TryGetValue(type, out var formatter))
            {
                return formatter;
            }

            if (TryInvokeRegisterFormatter(type))
            {
                // try again
                if (formatters.TryGetValue(type, out formatter))
                {
                    return formatter;
                }
            }

            var f = CreateGenericFormatter(type) as ILFormatter; 
            formatter = f ?? new ErrorSerializeFormatter(type);

            formatters[type] = formatter;
            return formatter;
        }

        static bool TryInvokeRegisterFormatter(Type type)
        {
            if (typeof(ILFormatterRegister).IsAssignableFrom(type))
            {
                // currently C# can not call like `if (T is IMemoryPackFormatterRegister) T.RegisterFormatter()`, so use reflection instead.
                var m = type.GetMethod("RegisterFormatter",
                    BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
                if (m == null)
                {
                    throw new InvalidOperationException(
                        "Type implements ILFormatterRegister but can not found RegisterFormatter. Type: " +
                        type.FullName);
                }

                m!.Invoke(null, null); // Cache<T>.formatter will set from method
                return true;
            }
            return false;
        }

        static class Check<T>
        {
            public static bool registered;
        }
        
        public static class Cache<T>
        {
            public static LFormatter<T> formatter = default!;

            static Cache()
            {
                if (Check<T>.registered) return;
                try
                {
                    var type = typeof(T);
                    if (TryInvokeRegisterFormatter(type))
                    {
                        return;
                    }
                    
                    bool isUnmanaged = !RuntimeHelpers.IsReferenceOrContainsReferences<T>();
                    var f = CreateGenericFormatter(type, isUnmanaged) as LFormatter<T>; 
                    formatter = f ?? new ErrorSerializeFormatter<T>();
                }
                catch (Exception e)
                {
                    formatter = new ErrorSerializeFormatter<T>(e);
                } 
                Check<T>.registered = true;
            }
        }
                    
        public static object CreateGenericFormatter(Type type, bool isUnmanaged = false)
        {
            Type formatterType = null;
            
            formatterType = TryCreateArrayFormatterType(type, isUnmanaged);
            if (formatterType != null) goto CREATE;
            
            if (type.IsEnum || isUnmanaged)
            {
                formatterType = typeof(UnsafeUnmanagedFormatter<>).MakeGenericType(type);
                goto CREATE;
            }
            
            formatterType = TryCreateGenericFormatterType(type, CollectionFormatters);
            if (formatterType != null) goto CREATE;
            
            return null; 
            
            CREATE:
            return Activator.CreateInstance(formatterType);
        }
        
        static Type TryCreateArrayFormatterType(Type type, bool isUnmanaged)
        {
            if (type.IsArray)
            {
                if (type.IsSZArray)
                {
                    return isUnmanaged
                        ? typeof(UnmanagedFormatter64<>).MakeGenericType(type.GetElementType()!)
                        : typeof(ArrayFormatter<>).MakeGenericType(type.GetElementType()!);
                }
            }
            return null;
        }
        
        static Type TryCreateGenericFormatterType(Type type, IDictionary<Type, Type> knownTypes)
        {
            if (type.IsGenericType)
            {
                var genericDefinition = type.GetGenericTypeDefinition();

                if (knownTypes.TryGetValue(genericDefinition, out var formatterType))
                {
                    return formatterType.MakeGenericType(type.GetGenericArguments());
                }
            }
            return null;
        }
    }
}
